package com.jin.webvideo.base.dc;

import java.io.IOException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Modifier;
import java.security.InvalidParameterException;

/**
 * 动态脚本执行器
 * @author biao532
 * @date 2023/12/02 23:10
 **/
public abstract class DSExecutor {
    private DCClassLoaderCreator creator;
    private String className;
    private ClassLoader parent;
    public DSExecutor(DCClassLoaderCreator creator, String className, ClassLoader parent) {
        this.creator = creator;
        this.className = className;
        this.parent = parent;
    }

    public DSExecutor(DCClassLoaderCreator creator, String className) {
        this(creator, className, Thread.currentThread().getContextClassLoader());
    }

    private static<T> SoftReference<T> softRef(T data) {
        return new SoftReference<T>(data);
    }
    private static<T> T weakGet(WeakReference<T> t) throws IOException {
        if (t.get() == null) {
            throw new IOException("");
        }
        return t.get();
    }

    public void exec() throws ScriptException {
        try {
            // classloader、class、obj 局部变量保证gc时被回收
            ClassLoader loader = creator.createClassLoader(parent);
            Class<?> clazz = loader.loadClass(className);
            if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers()) ||
                    !Modifier.isPublic(clazz.getModifiers()) || clazz.isAnonymousClass()) {
                throw new ScriptException("请检查类[%s]是否符合相关格式");
            }
            // DCScript只允许通过继承父类来获得，动态脚本禁止使用DCScript类
            DCScript s = clazz.getAnnotation(DCScript.class);
            if (s == null) {
                throw new ScriptException(String.format("类[%s]中未找到注解[@DCScript]", className));
            }
            String main = s.main();
            if (s.isStatic()) {
                invokeStatic(clazz, main);
                return;
            }
            invokeVirtual(clazz, main);
        }
        catch (Throwable e) {
            e.printStackTrace();
            throw new ScriptException(e.getMessage(), e.getCause());
        }
    }

    /**
     * 执行实例方法
     * @param clazz
     * @param mainMethod
     * @return
     * @throws Throwable
     */
    private void invokeVirtual(Class<?> clazz, String mainMethod) throws Throwable {
        MethodHandles.Lookup lookup = MethodHandles.publicLookup();
        Object[] params = args();
        // 创建对象
        MethodType c = findMethodType(params);
        MethodHandle constructor = lookup.findConstructor(clazz, c);
        Object obj = invoke(constructor, params);
        // 执行方法
        MethodType a = findMethodType(null);
        MethodHandle m = lookup.findVirtual(clazz, mainMethod, a);
        m.invoke(obj);
    }

    private void invokeStatic(Class<?> clazz, String mainMethod) throws Throwable {
        MethodHandles.Lookup lookup = MethodHandles.publicLookup();
        Object[] params = args();
        MethodType a = findMethodType(params);
        MethodHandle m = lookup.findStatic(clazz, mainMethod, a);
        invoke(m, params);
    }

    private static Object invoke(MethodHandle handle, Object[] params) throws Throwable {
        if (params == null || params.length == 0) {
            return handle.invoke();
        }
        return handle.invoke(params);
    }
    private static MethodType findMethodType(Object[] params) {
        if (params == null || params.length == 0) {
            return MethodType.methodType(void.class);
        }
        Class[] classes = new Class[params.length];
        for (int i = 0, l = params.length; i < l; i++) {
            if (params[i] == null) {
                throw new InvalidParameterException("不允许存在null");
            }
            classes[i] = params[i].getClass();
        }
        return MethodType.methodType(void.class, classes);
    }

    /**
     * 获取入参，如果是静态方法，则设置的是静态方法的入参， 如果是对象方法， 则设置的是构造方法的入参
     * 不允许设置null值
     * @return
     */
    protected abstract Object[] args();

}
